When a process is being debugged, debug events can be generated. A debugger typically listens and responds to the debug events. Debug events can be created when a process is created, when a module is loaded, when an exception is thrown, when a breakpoint is encountered and so on. A debugger uses the debug events and inspection application programming interfaces (APIs) to implement debugging operations. When a debug event is dispatched, the debuggee (the process being debugged) is typically stopped until the debugger continues the debug event. While the debuggee is stopped, the debugger can inspect the state of the debuggee. When the debuggee is continued, the process runs until the next debug event is encountered. Typically, managed code and native code debugging have different sets of debug events and different ways of transitioning between stopped and running states.
Native debugging is typically implemented by the operating system (OS). The OS provides the debugging API for listening to and continuing debug events. When a debug event occurs, the OS freezes the debuggee (the process being debugged). Native debugging is an out-of-process operation, that is, the process is frozen and code outside the process (in the OS) inspects values from the frozen process. Out-of-process debugging does not need cooperation from the debuggee. The native debuggee stop state is called frozen because the process has been stopped by the OS and no code executes inside the process until the OS starts the process up again.
Debugging in a virtual machine environment (e.g., in Eclipse or in Microsoft's Visual Studio®) is not performed by the operating system on a frozen process. Debugging in a virtual machine environment is typically implemented by the runtime environment of the virtual machine and is an in-process operation. Code inside the process executes to perform the debugging operations. The OS has no knowledge of when this type of debugging is occurring. The debuggee stop state is sometimes referred to as “synchronized” because a synchronized process is live from the OS perspective, but all the threads are stopped by the runtime environment. Virtual machine debug events may be “built on top of” or implemented using native debug events.
Virtual machine runtime services implement virtual machine debugging operations and do not implement native debug operations. Similarly, native debugging APIs do not provide support for debugging in a virtual machine environment. Debug operations in a virtual machine environment and native debug operations are implemented differently. For example, in Visual Studio®, managed stepping is implemented via a runtime environment API while native stepping is implemented via a native debug library that consumes native debug events. Native execution control (control of stepping operations and breakpoints) is typically implemented using OS exception processing and lacks explicit support in the native debugging API. The managed debugging API explicitly has breakpoint and stepping functionality. The difference in the abstraction levels of managed and native debugging prevents code sharing between managed and native debugging operations.
Some IDEs are able to debug code running in a virtual machine runtime environment and in a native runtime in a single debug session. This is called interoperative debugging. The mechanisms used in known interoperative debuggers are limited and rely on a tedious and fragile cooperation between the native and virtual machine runtime debuggers because of the underlying conflicting debugging architecture designs.